package com.leyou.item.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.rholder.retry.Retryer;
import com.leyou.common.exceptions.LyException;
import com.leyou.item.dto.Category;
import com.leyou.item.dto.CategoryRootDTO;
import com.leyou.item.mapper.CategoryMapper;
import com.leyou.item.service.CategoryService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * @author 虎哥
 */
@Slf4j
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {

    private final StringRedisTemplate redisTemplate;
    private final ObjectMapper objectMapper;
    private final RedisRetryServiceImpl redisRetry;
    private final Retryer retryer;


    public CategoryServiceImpl(StringRedisTemplate redisTemplate, ObjectMapper objectMapper,
                               RedisRetryServiceImpl redisRetry,Retryer retryer) {
        this.redisTemplate = redisTemplate;
        this.objectMapper = objectMapper;
        this.redisRetry = redisRetry;
        this.retryer = retryer;
    }

    private final String CATEGORIES_KEY = "categories:all";


    /**
     * 给前端返回分类信息，自带1，2，3级分层
     *
     * @return
     */
    @Override
    public String getAll() {
        String key = UUID.randomUUID().toString();
        String json = redisTemplate.opsForValue().get(CATEGORIES_KEY);
        if (json == null) {
            Long isGetLock = null;
            try {
                //1.尝试获取分布式锁，获取过程会如果失败会自动重试，间隔1秒，共执行3次，锁过期时间为5秒。
                isGetLock = (Long) retryer.call(() -> redisRetry.keyLock(key));
            } catch (Exception e) {
                throw new LyException(423,"网络繁忙，请稍后再试！");
            }
            //2.获得锁的情况将执行 数据库请求，这一部分的请求方法加入了熔断机制，4.5秒后会超时将抛出异常。
            if (isGetLock == 1L) {
                try {
                    List<CategoryRootDTO> list = getCategoryRootDTOS();
                    json = toJson(list);
                    redisTemplate.opsForValue().set(CATEGORIES_KEY, json);
                } catch (Exception e){
                    throw new LyException(500,"网络繁忙，请稍后再试！");
                }finally {
                    //最终一定释放锁，但释放锁时会对锁的value（即传入的uuid）进行判断，避免对非本次请求 误解锁。
                    redisRetry.keyUnlock(key);
                    log.info("释放锁执行完成");
                }
            }
        }
        return json;
    }

    /**
     * 序列化类
     * @param list
     * @return
     */
    private String toJson(List<CategoryRootDTO> list) {
        String json = null;
        try {
            json = objectMapper.writeValueAsString(list);
        } catch (Exception e) {
            log.error("数据序列化异常,{}", list);
            throw new RuntimeException("数据序列化异常!");
        }
        return json;
    }

    /**
     * 数据库获取分类逻辑
     *
     * @return
     */
    @HystrixCommand(fallbackMethod="failureMethod",
            commandProperties = {
                    @HystrixProperty(name ="execution.isolation.thread.timeoutInMilliseconds",value = "4500"),
                    @HystrixProperty(name = "circuitBreaker.enabled",value = "false")
            })
    private List<CategoryRootDTO> getCategoryRootDTOS() {
        //1.获取1级类目
        List<Category> rootCategory = query().eq("parent_id", 0).lt("sort", 16).orderByAsc("sort").list();
        List<Long> rootIds = rootCategory.stream().map(Category::getId).collect(Collectors.toList());

        //2.根据1级类目id，获取二级类目
        List<Category> levelTwo = query().in("parent_id", rootIds).lt("sort", 10).list();
        List<Long> levelTwoIds = levelTwo.stream().map(Category::getId).collect(Collectors.toList());

        //3.二级类目以map集合收集
        Map<Long, List<Category>> levelTwoDTOMap = levelTwo.stream().collect(Collectors.groupingBy(Category::getParentId));
        //4.根据2级类目，获取三级类目信息
        Map<Long, List<Category>> subDTOs = query().in("parent_id", levelTwoIds).lt("sort", 10)
                .list().stream().collect(Collectors.groupingBy(Category::getParentId));

        return rootCategory.stream().map(x -> CategoryRootDTO.ofDTO(x, levelTwoDTOMap.get(x.getId()), subDTOs)).collect(Collectors.toList());
    }

    /**
     * 降级方法
     * @return
     */
    private List<CategoryRootDTO> failureMethod() {
        log.error("getCategoryRootDTOS获取信息失败！");
        throw new RuntimeException();
    }

}
